--- 模块功能：根据基站信息查询经纬度
-- @module lbsLoc
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.25
require "socket"
require "utils"
require "common"
require "misc"
module(..., package.seeall)

require "socket"
require "utils"
require "common"
require "misc"
module(..., package.seeall)

local function enCellInfo(s)
    local ret, t, cntrssi = "", {}
    log.info("lbsLoc.enCellInfo", s)
    for mcc, mnc, lac, ci, rssi in string.gmatch(s, "(%d+)%.(%d+)%.(%d+)%.(%d+)%.(%d+);") do
        mcc, mnc, lac, ci, rssi = tonumber(mcc), tonumber(mnc), tonumber(lac), tonumber(ci), (tonumber(rssi) > 31) and 31 or tonumber(rssi)
        local handle = nil
        for k, v in pairs(t) do
            if v.lac == lac and v.mcc == mcc and v.mnc == mnc then
                if #v.rssici < 8 then
                    table.insert(v.rssici, {rssi = rssi, ci = ci})
                end
                handle = true
                break
            end
        end
        if not handle then
            table.insert(t, {mcc = mcc, mnc = mnc, lac = lac, rssici = {{rssi = rssi, ci = ci}}})
        end
    end
    -- for k, v in pairs(t) do
    --     ret = ret .. pack.pack(">HHb", v.lac, v.mcc, v.mnc)
    --     for m, n in pairs(v.rssici) do
    --         cntrssi = bit.bor(bit.lshift(((m == 1) and (#v.rssici - 1) or 0), 5), n.rssi)
    --         ret = ret .. pack.pack(sys.is8955 and ">bH" or ">bi", cntrssi, n.ci)
    --     end
    -- end

    return t
end

local function enWifiInfo(tWifi)
    local ret, cnt = "", 0
    if tWifi then
        for k, v in pairs(tWifi) do
            log.info("lbsLoc.enWifiInfo", k, v)
            ret = ret .. pack.pack("Ab", (k:gsub(":", "")):fromHex(), (v < 0) and (v + 255) or v)
            cnt = cnt + 1
        end
    end
    return (tWifi and string.char(cnt) or "") .. ret
end

local function taskClient(cbFnc, timeout, productKey, host, port, reqTime, reqWifi)
    while not socket.isReady() do
        if not sys.waitUntil("IP_READY_IND", timeout) then
            sys.publish("LBSLOC_RESULT", false, false)
            if cbFnc then cbFnc(1) end
            return
        end
    end
    local retryCnt = 0
    while true do
        -- 获取基站数据
        log.info("lbsLoc.query", "发起lbs定位请求")
        local cell = json.encode(enCellInfo(net.getCellInfoExt())) 
        log.info("reqStr", cell)
        local param = {project_key = ""..productKey, imei = ""..misc.getImei(), req = cell}
        local url = host.."/api/v1/device/lbs"
        code, head, body = httpv2.request("POST",  url, 30000, nil,  param, 1)
        local errcode = tonumber(code)
        if errcode == 200 and body then    
            local dat, res, err = json.decode(body)
            if res and tonumber(dat.errno) == 0 then       
                sys.publish("LBSLOC_RESULT", dat.data.lon, dat.data.lat)
                if cbFnc then
                    cbFnc(0, dat.data.lon, dat.data.lat, nil, dat.data.at, "LBS")
                end
                log.info("lbsLoc.query", "定位成功!")
                return
            else
                log.warn("lbsLoc.query", "返回的参数错误:", dat.error)
                sys.publish("LBSLOC_RESULT", false, false)
                if cbFnc then cbFnc(5) end
                return
            end
            return
        else
            log.warn("lbsLoc.query", "根据基站查询经纬度失败")
            if  errcode == 401 then
                log.warn("lbsLoc.query", "DEVLINK_KEY用户识别失败，请去检查")
            elseif  errcode == 400 then
                log.warn("lbsLoc.query", "上传参数错误")
            elseif  errcode == 403 then
                log.warn("lbsLoc.query", "获取失败")
            end
            sys.wait(30000)
            retryCnt = retryCnt + 1
            if retryCnt >= 3 then
                sys.publish("LBSLOC_RESULT", false, false)
                if cbFnc then cbFnc(2) end
                return
            end
        end
    end
end

--- 发送根据基站查询经纬度请求（仅支持中国区域的位置查询）
-- @function cbFnc，用户回调函数，回调函数的调用形式为：
--              cbFnc(result,lat,lng,addr,dateTime,locType)
--              result：number类型
--                      0表示成功
--                      1表示网络环境尚未就绪
--                      2表示连接服务器失败
--                      3表示发送数据失败
--                      4表示接收服务器应答超时
--                      5表示服务器返回查询失败
--                      6表示socket已满，创建socket失败
--                      为0时，后面的5个参数才有意义
--              lat：string类型或者nil，纬度，整数部分3位，小数部分7位，例如"031.2425864"
--              lng：string类型或者nil，经度，整数部分3位，小数部分7位，例如"121.4736522"
--              addr：无意义，保留使用
--              dateTime：无意义，保留使用
--              locType：string类型，位置类型，"LBS"表示基站定位位置，"WIFI"表示WIFI定位位置
-- @bool[opt=nil] reqAddr，此参数无意义，保留
-- @number[opt=20000] timeout，请求超时时间，单位毫秒，默认20000毫秒
-- @string[opt=nil] productKey，IOT网站上的产品证书，此参数可选，用户如果在main.lua中定义了PRODUCT_KEY变量，就不需要传入此参数
-- @string[opt=nil] host，服务器域名，此参数可选，目前仅lib中agps.lua使用此参数。用户脚本中不需要传入此参数
-- @string[opt=nil] port，服务器端口，此参数可选，目前仅lib中agps.lua使用此参数。用户脚本中不需要传入此参数
-- @bool[opt=nil] reqTime，是否需要服务器返回时间信息，true返回，false或者nil不返回，此参数可选，目前仅lib中agps.lua使用此参数。用户脚本中不需要传入此参数
-- @table[opt=nil] reqWifi，搜索到的WIFI热点信息(MAC地址和信号强度)，如果传入了此参数，后台会查询WIFI热点对应的经纬度，此参数格式如下：
--              {
--                  ["1a:fe:34:9e:a1:77"] = -63,
--                  ["8c:be:be:2d:cd:e9"] = -81,
--                  ["20:4e:7f:82:c2:c4"] = -70,
--              }
-- @return string,string, 在任务中使用，返经度，纬度的字符串
-- @usage local lng,lat = lbsLoc.request() -- 仅支持任务中使用
-- @usage lbsLoc.request(cbFnc)
-- @usage lbsLoc.request(cbFnc,true)
-- @usage lbsLoc.request(cbFnc,nil,20000)
function request(cbFnc, timeout, productKey, host, port, reqTime, reqWifi)
    productKey = productKey or io.authKey or "v32xEAKsGTIEQxtqgwCldp5aPlcnPs3K"
    sys.taskInit(taskClient, cbFnc, timeout or 20000, productKey, host or "open.developlink.cloud", port or "12411", reqTime, reqWifi)
    if coroutine.running() then
        local res, lng, lat = sys.waitUntil("LBSLOC_RESULT")
        return res and lng, res and lat
    end
end
